//============================================================================
// Name        : sgpHeuristics.cpp
// Author      : Ruslan Fayzrakhmanov
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <string>
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>

#include <pthread.h>
#include <stdlib.h>


#include "Lib.h"
#include "Search.h"
#include "SearchWithArrays.h"
#include "TabuSearch.h"

using namespace std;

int WEEKS = 7;
int GROUPS_IN_WEEK = 8;
int PLAYERS_IN_GROUP = 8;
long MAX_ITERATION = 1000;
long MAX_DURATION_IN_MSec = -1;

int WHICH_ALG = 1;

// Parameters for Tabu search
int START_INIT_ALG = 0;
long FAST_SOLUTION_MAX_ITERATION = 1000000;
int MIN_PLAYER_PENALTY = 4;
int MAX_PLYAER_PENALTY = 100;
int MAX_NEIGHBOURS = -1;
int TABLE_CHANGE_REGIME = 0;
int MAX_WAITING_OF_PROGRESS = 20;
int MAX_SOFT_PERTURBATIONS = 3;
int FREQUENCY_FOR_LAST_ITERATIONS = 100;
int CHOOSE_OFTEN_PLYAER_SWAPS = 0;



// true - thread was finished successfuly, false - thread was stopped
bool threadIsFinished = true;

// Additional function to start ScheduleIt() in new process
void *ScheduleIt( void *ptr )
{
	Search *search;
	search = (Search *) ptr;
	search->ScheduleIt();
    threadIsFinished = true;
    return NULL;
}

// TODO: Implement a correct ending for thread
bool search(Search* search, string algName) {
	const static long tick = 1000;
	static long MAX_DURATION_IN_MKSec = MAX_DURATION_IN_MSec * 1000;
	bool threadWasKilled = false;
	string strTimeDifFromStart, strTimeDifFromPrev;
	Lib::getTimeDiffInMilliseconds(strTimeDifFromStart, strTimeDifFromPrev, true);
			string txtOutput = "=== OUTPUT ===\n" + strTimeDifFromStart + " (" + strTimeDifFromPrev + ") -- Start. Algorithm " + algName;
			string strNumbers;
			txtOutput.append(" (");
			{
				std::stringstream out;
				out << GROUPS_IN_WEEK;
				strNumbers += out.str();
			}
			strNumbers.append("-");
			{
				std::stringstream out;
				out << PLAYERS_IN_GROUP;
				strNumbers += out.str();
			}
			strNumbers.append("-");
			{
				std::stringstream out;
				out << WEEKS;
				strNumbers += out.str();
			}

			txtOutput +=strNumbers;
			txtOutput.append(")\n");
	search->init();
	Lib::getTimeDiffInMilliseconds(strTimeDifFromStart, strTimeDifFromPrev, false);
			txtOutput += strTimeDifFromStart + " ("+ strTimeDifFromPrev+") -- Initialisation.\n";
	if (MAX_DURATION_IN_MKSec>0) {
		threadIsFinished = false;
		pthread_t threadId;
		int  result;
		result = pthread_create( &threadId, NULL, ScheduleIt, (void*) search );
		if (result != 0) {
			cout<<"ERROR: Can not create the thread."<<endl<<flush;
			return false;
		}
		long msLeft = 0;
		while (msLeft<MAX_DURATION_IN_MKSec && !threadIsFinished) {
			usleep(tick);
			msLeft+=tick;
		}
		if (!threadIsFinished) {
			search->stopScheduleGeneration();
			pthread_join(threadId, NULL);
			threadWasKilled = true;
		}
	}
	else {
		search->ScheduleIt();
		threadIsFinished = true;
	}
	Lib::getTimeDiffInMilliseconds(strTimeDifFromStart, strTimeDifFromPrev, false);
			txtOutput += strTimeDifFromStart + " (" + strTimeDifFromPrev + ") -- Algorithm Imlementation.\n--- Notes:\nIterations: ";
			{
				std::stringstream out;
				out << search->getCurrIteration();
				txtOutput += out.str() + "\n";
			}
			txtOutput += "Best Iteration: ";
			{
				std::stringstream out;
				out << search->getBestIteration();
				txtOutput += out.str() + "\n";
			}
			if (threadWasKilled)
				txtOutput += "Algorithm was stopped because time was exceeded.\n";
			else if (search->getStatus() == -1)
				txtOutput += "It is not possible to create the schedule.\n";
			else if (search->getStatus() == -2)
				txtOutput += "Algorithm was stopped because the iterations were exceeded.\n";
			if (threadWasKilled || search->getStatus() != 0)
				txtOutput += "Solution was not found.\n";
			else
				txtOutput += "Solution was found.\n";
			txtOutput += "---\n";
		list<int*> lstErrors = Lib::checkSchedule(search->getBestSchedule(), WEEKS, GROUPS_IN_WEEK, PLAYERS_IN_GROUP, WEEKS-1, GROUPS_IN_WEEK-1, PLAYERS_IN_GROUP-1);
			string topText = "Algorithm: ";
			topText.append(algName);
			topText += " ( "+ strNumbers + ")<br>";
			Lib::getTimeDiffInMilliseconds(strTimeDifFromStart, strTimeDifFromPrev, false);
			txtOutput += strTimeDifFromStart + " (" + strTimeDifFromPrev + ") " + "-- Finish.\n";
			txtOutput += "\n=== PARAMETERS ===\n" + search->getParametersAsString();
			{
				std::stringstream out;
				out<< "MAX_DURATION_IN_MSec = " << MAX_DURATION_IN_MSec <<"  (limit of time for the algorithm, -1 - not set)"<< endl;
				txtOutput.append(out.str());
			}
			cout<< txtOutput << endl <<flush;
			txtOutput = "<pre>"+txtOutput+"</pre>";
		string html = Lib::htmlRepresentOfSchedule(search->getBestSchedule(), WEEKS, GROUPS_IN_WEEK, PLAYERS_IN_GROUP, lstErrors, topText, txtOutput);
			string fileName = "output/" + algName + "-" + strNumbers+".htm";
			ofstream fout(fileName.c_str());
			fout << html << endl;
			fout.close();
	return true;
}

void usage () {
	cout<<"Usage:"<<endl;
	cout<<"-WHICH_ALG "<<" (0 - INIT_ALG algorithm, 1 - TABU algorithm)" <<endl;
	cout<<"-WEEKS "<<endl;
	cout<<"-GROUPS_IN_WEEK " <<endl;
	cout<<"-PLAYERS_IN_GROUP "<<endl;
	cout<<"-MAX_ITERATION "<<endl;
	cout<<"-MAX_DURATION_IN_MSec "<<endl;
	cout<<"-START_INIT_ALG "<<" (0 - don't use INIT_ALG algorithm, 1 - use INIT_ALG algorithm)" <<endl;
	cout<<"-FAST_SOLUTION_MAX_ITERATION "<<" (limit of iterations for INIT_ALG algorithm, if START_INIT_ALG = 1, -1 - not set)" <<endl;
	cout<<"-MIN_PLAYER_PENALTY "<<" (the minimum time a pair of golfers stays in the tabu list)" <<endl;
	cout<<"-MAX_PLYAER_PENALTY "<<" (the maximum time a pair of golfers stays in the tabu list)" <<endl;
	cout<<"-MAX_NEIGHBOURS "<<" (maximum amount of neighbourhood solutions, -1 - not set)" <<endl;
	cout<<"-TABLE_CHANGE_REGIME "<<" (0 - don't change table's order, 1 - change table order every iteration, first table becomes to be the last one)" <<endl;
	cout<<"-MAX_WAITING_OF_PROGRESS "<<" (maximum amount of iterations without progress, after that perturbation is invoked)" <<endl;
	cout<<"-MAX_SOFT_PERTURBATIONS "<<" (maximum perturbations after which tabu search starts again from last solution)" <<endl;
	cout<<"-FREQUENCY_FOR_LAST_ITERATIONS "<<" (quantity of iteration after which information about frequency for every swap are deleted)" <<endl;
	cout<<"-CHOOSE_OFTEN_PLYAER_SWAPS "<<" (-1 - choose first best swap, 0 - choose more rarely chosen swap from the best, 1 - choose more often chosen swap from the best)" << endl;
}

int main(int argc, char *argv[]) {
	for (int i=1; i<argc; i++) {
		if (strcmp("-WHICH_ALG",argv[i]) == 0)
			WHICH_ALG = atoi(argv[i+1]);
		else if (strcmp("-WEEKS",argv[i]) == 0)
			WEEKS = atoi(argv[i+1]);
		else if (strcmp("-GROUPS_IN_WEEK",argv[i]) == 0)
			GROUPS_IN_WEEK = atoi(argv[i+1]);
		else if (strcmp("-PLAYERS_IN_GROUP",argv[i]) == 0)
			PLAYERS_IN_GROUP = atoi(argv[i+1]);
		else if (strcmp("-MAX_ITERATION",argv[i]) == 0)
			MAX_ITERATION = atoi(argv[i+1]);
		else if (strcmp("-MAX_DURATION_IN_MSec",argv[i]) == 0)
			MAX_DURATION_IN_MSec = atoi(argv[i+1]);
		else if (strcmp("-START_INIT_ALG",argv[i]) == 0)
			START_INIT_ALG = atoi(argv[i+1]);
		else if (strcmp("-FAST_SOLUTION_MAX_ITERATION",argv[i]) == 0)
			FAST_SOLUTION_MAX_ITERATION = atoi(argv[i+1]);
		else if (strcmp("-MIN_PLAYER_PENALTY",argv[i]) == 0)
			MIN_PLAYER_PENALTY = atoi(argv[i+1]);
		else if (strcmp("-MAX_PLYAER_PENALTY",argv[i]) == 0)
			MAX_PLYAER_PENALTY = atoi(argv[i+1]);
		else if (strcmp("-MAX_NEIGHBOURS",argv[i]) == 0)
			MAX_NEIGHBOURS = atoi(argv[i+1]);
		else if (strcmp("-TABLE_CHANGE_REGIME",argv[i]) == 0)
			TABLE_CHANGE_REGIME = atoi(argv[i+1]);
		else if (strcmp("-MAX_WAITING_OF_PROGRESS",argv[i]) == 0)
			MAX_WAITING_OF_PROGRESS = atoi(argv[i+1]);
		else if (strcmp("-MAX_SOFT_PERTURBATIONS",argv[i]) == 0)
			MAX_SOFT_PERTURBATIONS = atoi(argv[i+1]);
		else if (strcmp("-FREQUENCY_FOR_LAST_ITERATIONS",argv[i]) == 0)
			FREQUENCY_FOR_LAST_ITERATIONS = atoi(argv[i+1]);
		else if (strcmp("-CHOOSE_OFTEN_PLYAER_SWAPS",argv[i]) == 0)
			CHOOSE_OFTEN_PLYAER_SWAPS = atoi(argv[i+1]);
		else if (strcmp("-help",argv[i]) == 0 || strcmp("-usage",argv[i]) == 0) {
			usage();
			return 1;
		}
	}
	if (argc == 1) {
		usage();
		return 1;
	}

	cout<<"Application was started.\n\n"<<flush;

if (WHICH_ALG == 0) {
	Search* searchWithArrays = new SearchWithArrays(WEEKS, GROUPS_IN_WEEK, PLAYERS_IN_GROUP, MAX_ITERATION);
	search(searchWithArrays, "EXHAUSTIVE_SEARCH");
	delete searchWithArrays;
}
else
{
	TabuSearch* tabuSearch = new TabuSearch(WEEKS, GROUPS_IN_WEEK, PLAYERS_IN_GROUP, MAX_ITERATION);
	tabuSearch->setAdditionalParams(START_INIT_ALG
			, FAST_SOLUTION_MAX_ITERATION
			, MIN_PLAYER_PENALTY
			, MAX_PLYAER_PENALTY
			, MAX_NEIGHBOURS
			, TABLE_CHANGE_REGIME
			, MAX_WAITING_OF_PROGRESS
			, MAX_SOFT_PERTURBATIONS
			, FREQUENCY_FOR_LAST_ITERATIONS
			, CHOOSE_OFTEN_PLYAER_SWAPS);
	search(tabuSearch , "TABU");
	delete tabuSearch;
}

	cout<<"Application was successfully terminates.\n"<<flush;
	return 0;
}
